package great.project.easyexcel.easyexcel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import great.project.easyexcel.easyexcel.bo.ExcelErrorMessage;
import great.project.easyexcel.easyexcel.bo.ExcelProgress;
import great.project.easyexcel.mock.ProcessMock;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

/**
 * <div  style="border:2px solid none;margin:20px;padding:15px;font-size:24px;text-align:center;color:aqua;">
 *
 *
 *
 *  </div>
 *
 * @author zhangshun
 * @date 2020/11/30 15:05
 */
public class ExcelEventListListener<T> extends AnalysisEventListener<T> {

    /**
     * 操作数据
     */
    private final Consumer<List<T>> consumer;

    /**
     * 校验数据
     * <p>
     * T 是需要检验的数据
     * Set 是如果需要去重的话 ，那就将去重对象放入进去 ， 再另行判断
     * ExcelErrorMessage 错误信息 只有在返回false的时候 这个信息才用到
     * Boolean 返回的结果 ， 返回true ， 代表成功
     */
    private final SFunction<T, Set<Object>, ExcelErrorMessage, Boolean> validateFunction;

    private final ProcessMock processMock;

    /**
     * 一共多少行数据
     */
    private final Integer allRowCount;

    /**
     * 进程key
     */
    private final String processKey;

    /**
     * 错误的条数
     */
    private Integer errorCount = 0;

    /**
     * 处理的总数
     */
    private Integer allCount = 0;

    /**
     * 当前的数据（等待插入导数据库中的）
     */
    List<T> list = new ArrayList<>();

    /**
     * 如果需要去重的话 使用该字段来去掉重复
     */
    final Set<Object> counter = new HashSet<>();

    final List<ExcelErrorMessage> excelErrorMessageList = new ArrayList<>();


    private static final int BATCH_COUNT = 3000;

    private final Consumer<T> fixData;

    public ExcelEventListListener(
            Consumer<List<T>> consumer
            , Consumer<T> fixData
            , SFunction<T, Set<Object>, ExcelErrorMessage, Boolean> validateFunction
            , ProcessMock processMock
            , Integer allRowCount
            , String processKey
    ) {
        this.consumer = consumer;
        this.validateFunction = validateFunction;
        this.processMock = processMock;
        this.allRowCount = allRowCount;
        this.processKey = processKey;
        this.fixData = fixData;
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
        // 错误数据信息 只有在错误时  该值才有内容 ， 正确的话 ， 即使有内容 也没有用
        final ExcelErrorMessage excelErrorMessage = new ExcelErrorMessage();
        allCount++;
        // 如果校验通过则 假如到数据库
        if (validateFunction.apply(data, counter, excelErrorMessage)) {
            // 补充数据
            fixData.accept(data);
            list.add(data);
        } else {
            // 错误数量增加
            errorCount++;
            excelErrorMessage.setRowNumber(context.readRowHolder().getRowIndex() + 1);
            excelErrorMessageList.add(excelErrorMessage);
        }

        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (list.size() >= BATCH_COUNT || excelErrorMessageList.size() > BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
        // 直接处理完毕
        updateProcess(context.readRowHolder().getRowIndex() + 1);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        // 直接处理完毕
        updateProcess(allRowCount);

    }


    private void saveData() {
        if (!list.isEmpty()) {
            consumer.accept(list);
        }
        if (!excelErrorMessageList.isEmpty()) {
            processMock.addErrorMessages(processKey, excelErrorMessageList);
            excelErrorMessageList.clear();
        }
    }

    /**
     * 更新进度
     *
     * @param now 当前的处理的条数
     */
    public void updateProcess(Integer now) {
        final ExcelProgress build = ExcelProgress.builder().currentTotal(allCount).now(now).total(allRowCount).error(errorCount).isBreak((byte) 0).build();
        processMock.updateProcess(processKey , build);
    }


}
